/*
 * @lc app=leetcode.cn id=127 lang=cpp
 *
 * [127] 单词接龙
 *
 * https://leetcode.cn/problems/word-ladder/description/
 *
 * algorithms
 * Hard (47.97%)
 * Likes:    1120
 * Dislikes: 0
 * Total Accepted:    164.4K
 * Total Submissions: 342.6K
 * Testcase Example:  '"hit"\n"cog"\n["hot","dot","dog","lot","log","cog"]'
 *
 * 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 ->
 * s2 -> ... -> sk：
 * 
 * 
 * 每一对相邻的单词只差一个字母。
 * 对于 1 <= i <= k 时，每个 si 都在 wordList 中。注意， beginWord 不需要在 wordList 中。
 * sk == endWord
 * 
 * 
 * 给你两个单词 beginWord 和 endWord 和一个字典 wordList ，返回 从 beginWord 到 endWord 的 最短转换序列
 * 中的 单词数目 。如果不存在这样的转换序列，返回 0 。
 * 
 * 
 * 示例 1：
 * 
 * 
 * 输入：beginWord = "hit", endWord = "cog", wordList =
 * ["hot","dot","dog","lot","log","cog"]
 * 输出：5
 * 解释：一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
 * 
 * 
 * 示例 2：
 * 
 * 
 * 输入：beginWord = "hit", endWord = "cog", wordList =
 * ["hot","dot","dog","lot","log"]
 * 输出：0
 * 解释：endWord "cog" 不在字典中，所以无法进行转换。
 * 
 * 
 * 
 * 提示：
 * 
 * 
 * 1 <= beginWord.length <= 10
 * endWord.length == beginWord.length
 * 1 <= wordList.length <= 5000
 * wordList[i].length == beginWord.length
 * beginWord、endWord 和 wordList[i] 由小写英文字母组成
 * beginWord != endWord
 * wordList 中的所有字符串 互不相同
 * 
 * 
 */

// @lc code=start
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        for (auto& word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!_wordId.count(endWord)) {
            return 0;
        }

        vector<int> disBegin(_nodeNum, INT_MAX);
        int beginId = _wordId[beginWord];
        disBegin[beginId] = 0;
        queue<int> queBegin;
        queBegin.emplace(beginId);

        vector<int> disEnd(_nodeNum, INT_MAX);
        int endId = _wordId[endWord];
        disEnd[endId] = 0;
        queue<int> queEnd;
        queEnd.emplace(endId);

        while (!queBegin.empty() && !queEnd.empty()) {
            int queBeginSize = queBegin.size();
            for (int i = 0; i < queBeginSize; ++i) {
                int nodeBegin = queBegin.front();
                queBegin.pop();
                if (disEnd[nodeBegin] != INT_MAX) {
                    return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1;
                }
                for (auto& it : _edges[nodeBegin]) {
                    if (disBegin[it] == INT_MAX) {
                        disBegin[it] = disBegin[nodeBegin] + 1;
                        queBegin.emplace(it); 
                    }
                }
            }

            int queEndSize = queEnd.size();
            for (int i = 0; i < queEndSize; ++i) {
                int nodeEnd = queEnd.front();
                queEnd.pop();
                if (disBegin[nodeEnd] != INT_MAX) {
                    return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1;
                }
                for (auto& it : _edges[nodeEnd]) {
                    if (disEnd[it] == INT_MAX) {
                        disEnd[it] = disEnd[nodeEnd] + 1;
                        queEnd.emplace(it);
                    }
                }
            }
        }

        return 0;
    }

private:
    unordered_map<string, int> _wordId;
    vector<vector<int>> _edges;
    int _nodeNum = 0;

    void addWord(string& word)
    {
        if (!_wordId.count(word)) {
            _wordId[word] = _nodeNum++;
            _edges.emplace_back();
        }
    }

    void addEdge(string& word)
    {
        addWord(word);
        int id1 = _wordId[word];
        for (char& ch : word) {
            char tmp = ch;
            ch = '*';
            addWord(word);
            int id2 = _wordId[word];
            _edges[id1].emplace_back(id2);
            _edges[id2].emplace_back(id1);
            ch = tmp;
        }
    }
};
// @lc code=end

